// SPDX-FileCopyrightText: Adam Evyčędo
//
// SPDX-License-Identifier: GPL-3.0-or-later

package xyz.apiote.bimba.czwek.data.traffic

import android.content.Context
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.merge
import xyz.apiote.bimba.czwek.R
import xyz.apiote.bimba.czwek.api.Error
import xyz.apiote.bimba.czwek.data.ResultFlowItem
import xyz.apiote.bimba.czwek.data.exceptions.BimbaException
import xyz.apiote.bimba.czwek.data.exceptions.DataSourceMissingException
import xyz.apiote.bimba.czwek.data.exceptions.UserException
import xyz.apiote.bimba.czwek.data.settings.SettingsRepository
import xyz.apiote.bimba.czwek.data.sources.GeocodingOfflineDataSource
import xyz.apiote.bimba.czwek.data.sources.Server
import xyz.apiote.bimba.czwek.data.sources.Transitous
import xyz.apiote.bimba.czwek.network.isNetworkAvailable
import xyz.apiote.bimba.czwek.repo.FeedInfo.Companion.ID_TRANSITOUS
import xyz.apiote.bimba.czwek.repo.Position

class TrafficRepository {
	fun queryPlaces(query: String, context: Context): Flow<ResultFlowItem> {
		val geocodingSettings = SettingsRepository().getGeocoding(context)
		val offlineDS = GeocodingOfflineDataSource()
		val onlineDs = Transitous()

		return if (isNetworkAvailable(context)) {
			if (geocodingSettings?.useOnline == true) {
				onlineDs.queryPlaces(context, query)
			} else if (geocodingSettings?.useOffline == true) {
				offlineDS.queryPlaces(query, context)
			} else {
				flowOf(DataSourceMissingException("geocoding data source missing"))
			}
		} else {
			if (geocodingSettings?.useOffline == true) {
				offlineDS.queryPlaces(query, context)
			} else if (geocodingSettings?.useOnline == true) {
				flowOf(
					UserException(
						"device is offline",
						Error(0, R.string.error_offline, R.drawable.error_net)
					)
				)
			} else {
				flowOf(DataSourceMissingException("geocoding data source missing"))
			}
		}
	}

	fun queryQueryables(
		query: String,
		context: Context,
		feeds: List<String>? = null,
		ignoreNotFound: Boolean = false
	): Flow<ResultFlowItem> {
		try {
			checkNetwork(context)
		} catch (e: BimbaException) {
			return flowOf(e)
		}

		val (bimbaEnabled, transitousEnabled) = SettingsRepository().let { settings ->
			Pair(
				settings.getServerFeeds(context).bimbaEnabled(),
				settings.getTransitous(context)?.let { it.enabled && it.useOnline } == true
			)
		}

		val feeds = feeds ?: SettingsRepository().getAllFeeds(context).ids()

		val transitousRequested = feeds.contains(ID_TRANSITOUS)
		val bimbaRequested = feeds.minus(ID_TRANSITOUS).isNotEmpty()

		val bimbaQueryables = if (bimbaEnabled && bimbaRequested) {
			Server.get(context).queryQueryables(query, context, ignoreNotFound)
		} else {
			flowOf()
		}
		val transitousQueryables = if (transitousEnabled || transitousRequested) {
			Transitous().queryStops(context, query, ignoreNotFound)
		} else {
			flowOf()
		}
		return listOf(bimbaQueryables, transitousQueryables).merge()

		// TODO offline
	}

	fun locateQueryables(position: Position, context: Context): Flow<ResultFlowItem> {
		try {
			checkNetwork(context)
		} catch (e: BimbaException) {
			return flowOf(e)
		}

		val (bimbaEnabled, transitousEnabled) = SettingsRepository().let { settings ->
			Pair(
				settings.getServerFeeds(context).bimbaEnabled(),
				settings.getTransitous(context)?.let { it.enabled && it.useOnline } == true
			)
		}

		val bimbaQueryables = if (bimbaEnabled) {
			Server.get(context).locateQueryables(position, context)
		} else {
			flowOf()
		}
		val transitousQueryables = if (transitousEnabled) {
			Transitous().locateQueryables(context, position)
		} else {
			flowOf()
		}
		return listOf(bimbaQueryables, transitousQueryables).merge()

		// TODO offline
	}

	fun getLocatablesIn(bl: Position, tr: Position, context: Context): Flow<ResultFlowItem> {
		try {
			checkNetwork(context)
		} catch (e: BimbaException) {
			return flowOf(e)
		}

		val (bimbaEnabled, transitousEnabled) = SettingsRepository().let { settings ->
			Pair(
				settings.getServerFeeds(context).bimbaEnabled(),
				settings.getTransitous(context)?.let { it.enabled && it.useOnline } == true
			)
		}

		val bimbaLocatables = if (bimbaEnabled) {
			Server.get(context).getLocatablesIn(context, bl, tr)
		} else {
			flowOf()
		}
		val transitousLocatables = if (transitousEnabled) {
			Transitous().locateLocatables(
				context,
				Position(bl.positionLatitude, tr.positionLongitude),
				Position(tr.positionLatitude, bl.positionLongitude)
			)
		} else {
			flowOf()
		}

		return listOf(bimbaLocatables, transitousLocatables).merge()

		// TODO offline
	}

	private fun checkNetwork(context: Context) {
		if (!isNetworkAvailable(context)) {
			throw UserException(
				"device is offline",
				Error(0, R.string.error_offline, R.drawable.error_net)
			)
		}
	}
}